package com.aaron.proxy;

import com.aaron.config.RpcServiceConfig;
import com.aaron.entity.RpcServiceProperties;
import com.aaron.enums.RpcErrorMessageEnum;
import com.aaron.enums.RpcResponseCodeEnum;
import com.aaron.exception.RpcException;
import com.aaron.remoting.dto.RpcRequest;
import com.aaron.remoting.dto.RpcResponse;
import com.aaron.remoting.transport.RpcRequestTransport;
import com.aaron.remoting.transport.netty.client.NettyRpcClient;
import com.aaron.remoting.transport.socket.SocketRpcClient;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

/**
 * 动态代理类。客户端通过代理类发送请求
 * 当动态代理对象调用方法时，它实际上会调用以下调用方法。
 * 正是因为动态代理，客户端调用的远程方法就像调用本地方法（中间进程被屏蔽）。
 * 这里使用的是JDK动态代理机制，需要实现InvocationHandler
 */
@Slf4j
public class RpcClientProxy implements InvocationHandler {

    private static final String INTERFACE_NAME = "interfaceName";

    // 注意com/aaron/remoting/transport路径下的请求接口，有两个实现类netty和socket
    private final RpcRequestTransport rpcRequestTransport;
    private final RpcServiceConfig rpcServiceConfig;

    // 构造方法主要是配置RpcServiceProperties
    public RpcClientProxy(RpcRequestTransport rpcRequestTransport, RpcServiceConfig rpcServiceConfig) {
        this.rpcRequestTransport = rpcRequestTransport;
        this.rpcServiceConfig = rpcServiceConfig;
    }

    public RpcClientProxy(RpcRequestTransport rpcRequestTransport) {
        this.rpcRequestTransport = rpcRequestTransport;
        this.rpcServiceConfig = new RpcServiceConfig();
    }

    // 这里的第三个参数this其实就是本类RpcClientProxy
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class<T> clazz) {
        // 三个参数的作用需要清楚
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
                                        new Class<?>[]{clazz},
                                        this);
    }

    /**
     * 实际是使用代理对象来调用方法，网络传输细节在此方法内。在invoke方法中对所有代理类统一调用。
     */
    @SneakyThrows
    @SuppressWarnings("unchecked")
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        log.info("invoked method: [{}]", method.getName());
        RpcRequest rpcRequest = RpcRequest.builder().methodName(method.getName())
                .parameters(args)
                .interfaceName(method.getDeclaringClass().getName())
                .paramTypes(method.getParameterTypes())
                .requestId(UUID.randomUUID().toString())
                .group(rpcServiceConfig.getGroup())
                .version(rpcServiceConfig.getVersion())
                .build();
        RpcResponse<Object> rpcResponse = null;

        // 选择使用netty或者Socket,netty使用CompletableFuture来异步得到结果
        if (rpcRequestTransport instanceof NettyRpcClient) {
            // sendRpcRequest方法发送请求，得到
            CompletableFuture<RpcResponse<Object>> completableFuture = (CompletableFuture<RpcResponse<Object>>) rpcRequestTransport.sendRpcRequest(rpcRequest);
            // 这一步其实是异步执行，等待UnprocessedRequests.complete()方法被执行。通过 completableFuture 得到客户端返回的结果
            rpcResponse = completableFuture.get();
        }
        if (rpcRequestTransport instanceof SocketRpcClient) {
            rpcResponse = (RpcResponse<Object>) rpcRequestTransport.sendRpcRequest(rpcRequest);
        }
        // 检查发送与响应对象的格式
        this.check(rpcResponse, rpcRequest);
        return rpcResponse.getData();
    }

    // 检查发送与响应对象的格式，做出异常判断
    private void check(RpcResponse<Object> rpcResponse, RpcRequest rpcRequest) {
        if (rpcResponse == null) {
            throw new RpcException(RpcErrorMessageEnum.SERVICE_INVOCATION_FAILURE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName());
        }
        // request与response的 requestId若不同 是错误的
        if (!rpcRequest.getRequestId().equals(rpcResponse.getRequestId())) {
            throw new RpcException(RpcErrorMessageEnum.REQUEST_NOT_MATCH_RESPONSE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName());
        }
        // 请求失败的编码的比较
        if (rpcResponse.getCode() == null || !rpcResponse.getCode().equals(RpcResponseCodeEnum.SUCCESS.getCode())) {
            throw new RpcException(RpcErrorMessageEnum.SERVICE_INVOCATION_FAILURE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName());
        }
    }
}
